package se.webbzon.oschi01.sound;

import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer.Info;

public class ClipSound implements FiniteSound {
	
	// The default mixer to use for the platform
	private static Info defaultMixer = null;
	
	// The source of this clip sound
	private final ClipSource source;
	
	// The clip used to play the sound
	private final Clip clip;
	
	// Whatever this sound is looping
	private boolean playing, looping;
	
	/*============================================================
	Constructors
	============================================================*/
	
	public ClipSound(AudioFormat format, byte[] data, int offset, int bufferSize) throws IOException, LineUnavailableException {
		this(new AudioBufferSource(format,data,offset,bufferSize));
	}
	
	public ClipSound(ClipSound sound) throws IOException, LineUnavailableException {
		this(sound.source);
	}
	
	protected ClipSound(ClipSource source) throws IOException, LineUnavailableException {
		if (defaultMixer == null)
			this.clip = AudioSystem.getClip();
		else try {
			this.clip = AudioSystem.getClip(defaultMixer);
		} catch (Exception e) {
			throw new IOException(e);
		}
		this.source = source;
		looping = false;
		playing = false;
		source.setupClip(clip);
		clip.addLineListener(new LineListenerImpl());
	}

	/*============================================================
	Public Methods
	============================================================*/
	
	/** Sets the default mixer for all new ClipSounds. **/
	public static final void setDefaultMixer(Info defaultMixer) {
		ClipSound.defaultMixer = defaultMixer;
	}
	
	/** Returns the default mixer for all new ClipSounds. **/
	public static final Info getDefaultMixer() {
		return defaultMixer;
	}
	
	@Override public synchronized void play() {
		if (clip.isOpen()) {
			clip.setMicrosecondPosition(0);
			clip.start();
			setPlaying(true);
		}
	}
	
	@Override public synchronized boolean isPlaying() {
		return playing;
	}

	@Override public synchronized void pause() {
		if (clip.isOpen()) {
			clip.stop();
			setPlaying(false);
		}
	}
	
	@Override public synchronized void stop() {
		pause();
		if (clip.isOpen()) {
			clip.close();
		}
	}
	
	@Override public synchronized void setLooping(boolean looping) {
		this.looping = looping;
	}
	
	@Override public synchronized boolean isLooping() {
		return looping;
	}

	@Override public synchronized long length() {
		if (!clip.isOpen())
			return -1;
		else
			return clip.getMicrosecondLength();
	}

	@Override public synchronized long pos() {
		if (!clip.isOpen())
			return -1;
		else
			return clip.getMicrosecondPosition();
	}

	@Override public synchronized boolean seek(long time) {
			if (!clip.isOpen())
				return false;
			clip.setMicrosecondPosition(time);
			return true;
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Sets whatever this sound is playing or not. **/
	private synchronized void setPlaying(boolean playing) {
		this.playing = playing;
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	private static final class AudioBufferSource implements ClipSource {
		
		private AudioFormat format;
		
		private byte[] data;
		
		private int offset, bufferSize;
		
		public AudioBufferSource(AudioFormat format, byte[] data, int offset, int bufferSize) {
			this.format = format;
			this.data = data;
			this.offset = offset;
			this.bufferSize = bufferSize;
		}

		@Override public void setupClip(Clip clip) throws IOException, LineUnavailableException {
			clip.open(format,data,offset,bufferSize);
		}

		@Override public FiniteSound createSound() throws SoundException {
			try {
				return new ClipSound(this);
			} catch (IOException | LineUnavailableException e) {
				throw new SoundException(e);
			}
		}
		
	}
	
	private class LineListenerImpl implements LineListener {

		@Override public void update(LineEvent event) {
			System.out.println(event);
			if (event.getType() == LineEvent.Type.STOP) {
				if (event.getFramePosition() == clip.getFrameLength()) {
					if (isLooping()) {
						clip.setFramePosition(0);
						clip.start();
					} else {
						setPlaying(false);
						clip.drain();
						clip.close();
						System.out.println("Closing: " + event.getLine());
					}
				}
			} 
		}
		
	}

}
